package com.kyub.spring.gears.web;

import com.kyub.spring.gears.introspect.PojoDescriptorFactory;
import com.kyub.spring.gears.model.*;
import com.kyub.spring.gears.services.ControllerDelegateService;
import com.kyub.spring.gears.services.DaoService;
import com.kyub.spring.gears.util.IntrospectionUtils;
import com.kyub.spring.gears.web.upload.FileUploadBean;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
import org.springframework.web.servlet.ModelAndView;

import javax.naming.ConfigurationException;
import javax.servlet.http.HttpServletResponse;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Main  controller for crud operations
 */
@Controller("crudController")
@SessionAttributes({"commandPojo", "commandUpload"})
public class CrudController {

    private static Logger logger = Logger.getLogger(CrudController.class);

    private static final String PARAM_ID_NAME = "id";

    @Autowired
    private PojoRegistry pojoRegistry;
    @Autowired
    private CrudControllerConfig crudConfig;

    private PojoDescriptorFactory factory;

    private static final String COMMAND_POJO_NAME = "commandPojo";

    private static final String BACK_POJO = "backPojo";

    private static final Map<Class, PropertyEditorSupport> pecache = new HashMap<Class, PropertyEditorSupport>();
    private static final Map<String, PojoDescriptor> pojoDescriptions = new HashMap<String, PojoDescriptor>();


    @RequestMapping("/{pojo}/list")
    public ModelAndView list(ModelMap model, @PathVariable("pojo") String pojo, WebRequest request) {
        ModelAndView v = buildModelAndView();

        final PojoDescriptor descriptor = loadPojoDescriptor(pojo);

        v.setViewName("/" + crudConfig.templateFolder + "/list");
        v.addObject("pojoDescriptor", descriptor);


        final DaoService service = pojoRegistry.getManager(pojo);

        if (service instanceof ControllerDelegateService) {
            v.addObject("filters", ((ControllerDelegateService) service).getFilters());
            v.addObject("searchable", true);
            v.addObject("pojoList", ((ControllerDelegateService) service).list(request));
        } else {

            v.addObject("pojoList", service.list());
        }

        return v;
    }

    private ModelAndView buildModelAndView() {
        ModelAndView v = new ModelAndView();
        v.addObject("gears_restfull_prefix", crudConfig.getResfullPrefix());
        return v;
    }


    private PojoDescriptor loadPojoDescriptor(String pojo) {
        PojoDescriptor pd = pojoDescriptions.get(pojo);
        if (pd == null) {
            try {
                if (factory == null) {
                    factory = new PojoDescriptorFactory(pojoRegistry);
                }
                pd = factory.buildDescriptorByClass(Class.forName(((PojoRegistryEntry) pojoRegistry.getRegistry().get(pojo)).getClassName()));
                pojoDescriptions.put(pojo, pd);
            } catch (ConfigurationException e) {
                logger.debug(e.toString());
                if (logger.isDebugEnabled())
                    e.printStackTrace();

            } catch (ClassNotFoundException e) {
                logger.debug(e.toString());
                if (logger.isDebugEnabled())
                    e.printStackTrace();
            }
        }
        return pd;
    }

    @RequestMapping("/{pojo}/view/{id}")
    public ModelAndView view(@PathVariable(PARAM_ID_NAME) Serializable id, ModelMap model, @PathVariable("pojo") String pojo) {
        ModelAndView v = buildModelAndView();

        v.addObject("pojoDescriptor", loadPojoDescriptor(pojo));

        v.addObject("pojo", pojoRegistry.getManager(pojo).load(id));
        v.setViewName("/" + crudConfig.templateFolder + "/view");
        return v;
    }

    @RequestMapping("/{pojo}/delete/{id}")
    public ModelAndView delete(@PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo) {
        final DaoService service = pojoRegistry.getManager(pojo);
        service.delete(service.load(id));
        ModelAndView v = new ModelAndView();
        v.setViewName("redirect:/" + crudConfig.resfullPrefix + "/" + pojo + "/list");
        return v;
    }

    /*
    http://localhost:8080/petClinic/clinic/pet/view/0
    http://localhost:8080/petClinic/clinic/pet/insert
    */
    @RequestMapping(value = "/{pojo}/insert", method = RequestMethod.GET)
    public ModelAndView setupEmptyForm(ModelMap model, WebRequest request, @PathVariable("pojo") String pojo) {
        ModelAndView v = buildModelAndView();
        v.addObject("commandUpload", new FileUploadBean());
        v.setViewName("/" + crudConfig.templateFolder + "/form");
        try {
            final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
            v.addObject("pojoDescriptor", descriptor);
            Object pojoInstance = descriptor.getPojoClass().newInstance();

            logger.debug("pojoInstance " + pojoInstance.getClass().getName());
            v.addObject(COMMAND_POJO_NAME, pojoInstance);

            v.addObject("relatedValues", this.factory.loadRealtedValues(descriptor, pojoRegistry));

        } catch (InstantiationException e) {
            logger.debug(e.toString());
            if (logger.isDebugEnabled())
                e.printStackTrace();
        } catch (IllegalAccessException e) {
            logger.debug(e.toString());
            if (logger.isDebugEnabled())
                e.printStackTrace();
        }
        return v;
    }

    @RequestMapping(value = "/{pojo}/update/{id}", method = RequestMethod.GET)
    public ModelAndView setupPopulatedForm(ModelMap model, WebRequest request, @PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo) {

        logger.debug("BackPojo: " + request.getParameter(BACK_POJO));
        ModelAndView v = buildModelAndView();
        v.addObject("commandUpload", new FileUploadBean());
        v.addObject("updateObject", true);
        v.setViewName("/" + crudConfig.templateFolder + "/form");

        final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
        v.addObject(COMMAND_POJO_NAME, pojoRegistry.getManager(pojo).load(id));
        v.addObject("relatedValues", this.factory.loadRealtedValues(descriptor, pojoRegistry));
        v.addObject("pojoDescriptor", descriptor);

        return v;
    }

    @RequestMapping(value = "/{pojo}/submit", method = RequestMethod.POST)
    public ModelAndView submit(@ModelAttribute(COMMAND_POJO_NAME) Object pojoInstance, BindingResult bindingResult, @PathVariable("pojo") String pojo, WebRequest request) {
        logger.debug("Submitted " + pojo + " ================ " + pojoInstance);
        final String backPojoName = request.getParameter(BACK_POJO);
        logger.debug("BackPojo: " + backPojoName);


        PojoRegistryEntry p = pojoRegistry.getPojoConf(pojo);


        final DaoService service = p.getDao();
        logger.debug("service instanceof ControllerDelegateService " + (service instanceof ControllerDelegateService));

        if (p.getValidator() != null) {

            p.getValidator().validate(pojoInstance, bindingResult);
        } else if (service instanceof ControllerDelegateService) {

            ((ControllerDelegateService) service).validate(pojoInstance, bindingResult);

        }


        ModelAndView v = new ModelAndView();
        if (bindingResult.hasErrors()) {
            v = buildModelAndView();
            v.setViewName("/" + crudConfig.templateFolder + "/form");
            final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
            v.addObject("pojoDescriptor", descriptor);
            v.addObject(COMMAND_POJO_NAME, pojoInstance);
            v.addObject("relatedValues", this.factory.loadRealtedValues(descriptor, pojoRegistry));

        } else {
            service.save(pojoInstance);
            String destinationPojo = pojo;
            if (backPojoName != null)
                destinationPojo = backPojoName;
            v.setViewName("redirect:/" + crudConfig.resfullPrefix + "/" + destinationPojo + "/list");
        }
        return v;
    }

    @RequestMapping("/{pojo}/download/{id}/{property}")
    public void download(@PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo, @PathVariable("property") String property, WebRequest request, HttpServletResponse response) {
        final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
        final StructPropertyDescriptor propertyDescriptor = (StructPropertyDescriptor) descriptor.getFormFiled(property);
        final FileLayout fileLayout = (FileLayout) propertyDescriptor.getLayout();
        final Object pojoInstance = pojoRegistry.getManager(pojo).load(id);


        try {

            final String path = (String) IntrospectionUtils.readPropertyValue(pojoInstance, propertyDescriptor.getName());
            final String fileName = path.substring(path.lastIndexOf("/") + 1);

            logger.debug("Path " + path);

            logger.debug("File name " + fileName);

            response.setContentType(fileLayout.getMimeType() + "; name=\"" + fileName + "\"");
            response.addHeader("content-disposition", "attachment; filename=\"" + fileName + "\"");

            response.getOutputStream().write(this.crudConfig.getFileStorage().load(path));
        } catch (Exception e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
    }

    @RequestMapping("/{pojo}/executeupload/{id}/{property}")
    public ModelAndView executeUpload(@PathVariable(PARAM_ID_NAME) Serializable id, @PathVariable("pojo") String pojo, @PathVariable("property") String property, MultipartHttpServletRequest request, @ModelAttribute("commandUpload") Object uploadBean) {
        ModelAndView v = buildModelAndView();
        final PojoDescriptor descriptor = loadPojoDescriptor(pojo);
        v.addObject("pojoDescriptor", descriptor);
        v.addObject("propertyName", property);
        //  final MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request.;
        final MultipartFile multipartFile = request.getFile("file");

        final String originalFileName = multipartFile.getOriginalFilename();
        final StructPropertyDescriptor propertyDescriptor = (StructPropertyDescriptor) descriptor.getFormFiled(property);
        final FileLayout fileLayout = (FileLayout) propertyDescriptor.getLayout();
        final String path = fileLayout.getPersistPath() + "/" + pojo + "_" + id + "_" + property + "_" + System.currentTimeMillis() + "." + fileLayout.getExtension();
        FileUploadBean bean = (FileUploadBean) uploadBean;
        byte[] file = bean.getFile();
        if (file == null) {
            logger.error("File size null!");
        } else {
            try {
                this.crudConfig.getFileStorage().save(pojo, id, property, fileLayout, originalFileName, file);
                v.addObject("path", path);
            } catch (IOException e) {
                if (logger.isDebugEnabled()) {
                    e.printStackTrace();
                }
                v.addObject("error", e.toString());
            }
        }


        v.setViewName("/" + crudConfig.templateFolder + "/upload");
        return v;
    }


    @RequestMapping("/{pojo}/viewrelated/{id}/{property}")
    public ModelAndView viewRelated(@PathVariable(PARAM_ID_NAME) long id, ModelMap model, @PathVariable("pojo") String pojo, @PathVariable("property") String property) {
        ModelAndView v = buildModelAndView();

        final PojoDescriptor pojoOwner = loadPojoDescriptor(pojo);
        v.addObject("pojoOwner", pojoOwner);
        logger.debug("pojoOwner " + pojoOwner);


        final CollectionPropertyDescriptor propertyDescriptor = (CollectionPropertyDescriptor) pojoOwner.getProperty(property);
        logger.debug("pojoDescriptor for property " + property + " " + propertyDescriptor.getPojoDescr());
        v.addObject("pojoDescriptor", propertyDescriptor.getPojoDescr());


        try {
            v.addObject("pojoList", IntrospectionUtils.readPropertyValue(pojoRegistry.getManager(pojo).load(id), property));
        } catch (Exception e) {
            logger.debug(e.toString());
            // if (logger.isDebugEnabled())
            e.printStackTrace();
        }
        v.setViewName("/" + crudConfig.templateFolder + "/viewrelated");
        return v;
    }

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(crudConfig.datePattern);
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
        binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());

        if (pecache.size() == 0) {
            Iterator i = this.pojoRegistry.getRegistry().entrySet().iterator();
            final PropertyEditorBuilder pb = new PropertyEditorBuilder();
            logger.debug("NESSUN PROPERTY EDITOR IN CHACHE!  ");
            while (i.hasNext()) {
                final Map.Entry o = (Map.Entry) i.next();
                final PojoRegistryEntry entry = (PojoRegistryEntry) o.getValue();
                logger.debug("Registro propety editor per  " + entry.getClassName());

                try {
                    pecache.put(Class.forName(entry.getClassName()), pb.buildPropertyEditorSupport(loadPojoDescriptor(o.getKey().toString()), entry.getDao()));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }


            }
        }

        Iterator i = this.pecache.entrySet().iterator();
        while (i.hasNext()) {
            final Map.Entry o = (Map.Entry) i.next();
            final PropertyEditorSupport entry = (PropertyEditorSupport) o.getValue();
            binder.registerCustomEditor((Class) o.getKey(), entry);
        }


    }

    public PojoRegistry getPojoRegistry() {
        return pojoRegistry;
    }

    public void setPojoRegistry(PojoRegistry pojoRegistry) {
        this.pojoRegistry = pojoRegistry;

    }

    public CrudControllerConfig getCrudConfig() {
        return crudConfig;
    }

    public void setCrudConfig(CrudControllerConfig crudConfig) {
        this.crudConfig = crudConfig;
    }
}
